package com.pignest.hotel.system;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.lang.StringUtils;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import org.dom4j.io.OutputFormat;
import org.dom4j.io.SAXReader;
import org.dom4j.io.XMLWriter;

/**
 * 权限配置
 * */
/*
<!-- 
directory 和 file 节点都有3个属性可设置: name, permission, alone
	name 对应相应的文件(夹)名
	permission 表示访问该节点及子节点需要的权限(继承上级节点权限配置, 多个权限以逗号分割)
	alone 表示是否为独立节点, 值为true时, 不继承上级节点的权限 
 -->
 */
@SuppressWarnings("unchecked")
public class P {
	
	public static final String PERMISSION_CONFIG_PATH = "permissions.xml";
	private static final HashSet<String> IGNORES = new HashSet<String>();
	private static final HashSet<String> TOP_IGNORES = new HashSet<String>();
	static {
//		IGNORES.add(".svn");	//.*
//		IGNORES.add("images");
//		IGNORES.add("js");
//		IGNORES.add("scripts");
//		IGNORES.add("Thumbs.db");
		
		TOP_IGNORES.add("images");
		TOP_IGNORES.add("css");
		TOP_IGNORES.add("scripts");
		TOP_IGNORES.add("WEB-INF");
		TOP_IGNORES.add("META-INF");
		TOP_IGNORES.add("test");
		TOP_IGNORES.add("others");
	}
	
	private String name;
	private boolean directory;
	private String permission;
	private boolean alone;
	private boolean special;
	
	private HashMap<String, P> children;
	private P parent;
	
	private static P p;
	
	private P() {
		directory = true;
		name = "<web>";
	}
	
	private P(String name, String permission, boolean directory, boolean alone, boolean spec) {
		this.name = name;
		this.directory  = directory;
		this.alone = alone;
		this.permission = permission;
		this.special = spec;
	}
	
	public static P get() {
		if (p == null) {
			try {
				InputStream in = P.class.getClassLoader().getResourceAsStream(PERMISSION_CONFIG_PATH);
				p = fromConfig(in);
				in.close();
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
		return p;
	}
	
	public String[] getPermissions(String path) {
		String[] ss = StringUtils.split(path, "/");
		P p = getP(ss, 0);
		if (p == null)
			p = this;
		StringBuilder sb = new StringBuilder();
		while (p != null) {
			if (p.permission != null)
				sb.append(p.permission).append(",");
			if (p.alone)
				break;
			p = p.parent;
		}
		return StringUtils.split(sb.toString(), ",");
	}
	
	private P getP(String[] path, int index) {
		if (children == null || index >= path.length)
			return this;
		P p = children.get(path[index]);
		if (p != null) {
			if (index == path.length - 1 || p.children == null || p.children.size() == 0)
				return p;
			return p.getP(path, index + 1);
		}
		return this;
	}
	
	private void addChild(P child) {
		if (!directory)
			throw new RuntimeException(name);
		if (children == null)
			children = new LinkedHashMap<String, P>();
		children.put(child.name, child);
		child.parent = this;
	}
	
	
	private static P fromConfig(InputStream in) {
		try {
		    SAXReader reader = new SAXReader();
			Document d = reader.read(in);
			Element root = d.getRootElement();
			List<Element> children = root.elements();
			P p = new P();
			for (Element child : children) {
				r(p, child, true);
			}
			return p;
		} catch (DocumentException e) {
			e.printStackTrace();
		}
		return null;
	}
	
	
	private static void r(P parent, Element e, boolean root) {
		String name = e.attributeValue("name");
		String permission = e.attributeValue("permission");
		boolean dir = "directory".equals(e.getName());
		boolean alone = "true".equals(e.attributeValue("alone"));
		boolean special = root && "true".equals(e.attributeValue("special"));
		P p = new P(name, permission, dir, alone, special);
		parent.addChild(p);
		if (dir) {
			List<Element> children = e.elements();
			for (Element child : children) {
				r(p, child, false);
			}
		}
	}
	
	public void updateStructure(String webPath) throws IOException {
		File tmp = File.createTempFile("permission", "xml");
		readStructure(new File(webPath), tmp);
		P p = P.fromConfig(new FileInputStream(tmp));
		p.updateStructure(this);
		List<P> specs = getSpecial();
		this.children = p.children;
		if (this.children != null && specs != null) {
			for (P p2 : specs) {
				this.children.put(p2.name, p2);
			}
		}
		tmp.delete();
	}
	
	private List<P> getSpecial(){
		if (children == null)
			return null;
		ArrayList<P> specs = new ArrayList<P>();
		for (P p : children.values()) {
			if (p.special)
				specs.add(p);
		}
		return specs;
	}
	
	private void updateStructure(P p) {
		if (p == null)
			return;
		permission = p.permission;
//		directory = p.directory;
		alone = p.alone;
		if (children == null || p.children == null)
			return;
		for (Map.Entry<String, P> entry : children.entrySet()) {
			String name = entry.getKey();
			P pp = entry.getValue();
			pp.updateStructure(p.children.get(name));
		}
	}
	
	/**
	 * 写到文件
	 * */
	public void writeFile(File file) throws IOException {
		renderFile(toDocument(), file);
	}
	
	private Document toDocument() {
		Document d = DocumentHelper.createDocument();
		Element root = d.addElement("root");
		if (children == null || children.isEmpty())
			return d;
		for (P p : children.values()) {
			r(root, p, true);
		}
		return d;
	}
	
	private void r(Element parent, P p, boolean root) {
		Element e = p.directory ? parent.addElement("directory") : parent.addElement("file");
		e.addAttribute("name", p.name);
		if (StringUtils.isNotEmpty(p.permission))
			e.addAttribute("permission", p.permission);
		if (p.alone)
			e.addAttribute("alone", "true");
		if (root && p.special)
			e.addAttribute("special", "true");
		if (p.directory && p.children != null) {
			for (P pp : p.children.values()) {
				r(e, pp, false);
			}
		}
	}
	
	/**
	 * 读取目录结构, 并存储为XML
	 * */
	public static void readStructure(File webFile, File targetFile) throws IOException {
		Document d = renderDocument(webFile);
		renderFile(d, targetFile);
	}
	
	private static Document renderDocument(File file) {
		Document d = DocumentHelper.createDocument();
		Element root = d.addElement("root");
		if (file == null)
			return d;
		File[] files = file.listFiles();
		if (files == null || files.length == 0)
			return d;
		Arrays.sort(files, new Comparator());
		for (int i = 0; i < files.length; i++) {
			if (TOP_IGNORES.contains(files[i].getName()))
				continue;
			r(files[i], root);
		}
		return d;
	}

	private static void r(File file, Element parent) {
		String name = file.getName();
		if (name.charAt(0) == '.' || IGNORES.contains(name))
			return;
		if (file.isFile()) {
			Element e = parent.addElement("file");
			e.addAttribute("name", name);
//			e.addAttribute("permission", "");
		} else if (file.isDirectory()) {
			Element e = parent.addElement("directory");
			e.addAttribute("name", name);
			File[] files = file.listFiles();
			if (files == null || files.length == 0)
				return;
			Arrays.sort(files, new Comparator());
			for (int i = 0; i < files.length; i++) {
				r(files[i], e);
			}
		}
	}

	public static void renderFile(Document document, File file)
			throws IOException {
		FileWriter out = new FileWriter(file);
		OutputFormat format = OutputFormat.createPrettyPrint();
		XMLWriter writer = new XMLWriter(out, format);
		writer.write(document);
		writer.close();
	}

	@Override
	public String toString() {
		return (directory ? "D " : "  ") + name;
	}
	
	private static class Comparator implements java.util.Comparator<File> {

		@Override
		public int compare(File o1, File o2) {
			if (o1.isDirectory() && o2.isFile()) {
				return -1;
			} else if (o1.isFile() && o2.isDirectory()){
				return 1;
			}
//			int i = o1.getName().compareTo(o2.getName());
//			System.out.println(o1.getName() + " " + i + " " + o2.getName());
			return o1.getName().compareToIgnoreCase(o2.getName());
		}
		
	}
}
